/*
 * HttpServer.cpp
 *
 *  Created on: Mar 13, 2012
 *      Author: shaohong
 */


#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>
#include <strings.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sstream>

#define LISTENQ 5

#include "WebServer.h"
#include "AppLogger.h"
#include "HttpHelper.h"
#include "Utils.h"

using namespace std;

static AppLogger * logger = AppLogger::getInstance("WebServer");

struct WebServerThreadParams {
	WebServer * webServer;
	int connfd;
};

static void * serveRequest(void *arg)
{
	WebServerThreadParams * webserverThreadParams =
			static_cast<WebServerThreadParams*>(arg);

	WebServer * webServer = webserverThreadParams->webServer;

	webServer->increamentThreadNumber();

	// serve this request
	try {
		// serve this request
		webServer->service(webserverThreadParams->connfd);
	}catch (exception &e){
		logger->error("met exception ");
		close(webserverThreadParams->connfd);
	}

	webServer->decrementThreadNumber();

	return (void*)(0);
}

WebServer::WebServer(uint32_t port, string www_root)
{
	this->serverPort = port;
	this->www_root = www_root;
	this->serverShutingDown = false;

	this->threadNumber = 0;	// the number of running thread
	this->threadNumberMutex = PTHREAD_MUTEX_INITIALIZER;// the mutex to access running thread number

}

WebServer::~WebServer() {
	// TODO Auto-generated destructor stub
}

void WebServer::service(int connfd)
{
    // Read the (initial part of) HTTP Request
	char buffer[1500];
	memset(buffer, 0, sizeof(buffer));
	int bufsize = read(connfd, buffer, sizeof(buffer));

//	cerr << "initial read: " << bufsize << endl;

	// first, let's find the start of the content
	const char * crlfcrlf = "\r\n\r\n";
	char * pch = strstr(buffer, crlfcrlf);
	if (NULL == pch) {
		//What? can't find the 0d0a0d0a? cann't go further from here
		logger->error("Couldn't find the 0d0a0d0a pattern! return 400");

		HttpResponse * response = new HttpResponse();

		// we should return 400 Bad Request here
		response->setStatus(400, "Bad Request");
		sendResponse(response, connfd);

		close(connfd);
		return;
	}

	char * contentDataStart = pch + 4;
	int headerDataLength = contentDataStart - buffer;
	int contentDataLength = bufsize - headerDataLength;

	char tmp[1500];
	memset(tmp, 0, sizeof(tmp));
	memcpy(tmp, buffer, headerDataLength);

	logger->debug("received request:\n%s", tmp);
	HttpRequest * request = HttpHelper::parseHttpRequest(string(tmp));

	if (0 == request->method.compare("GET")){

		doGet(*request, connfd);

	} else if (0 == request->method.compare("PUT")){

		doPut(*request, connfd, contentDataStart, contentDataLength);

	} else {
		logger->error("unknown method: %s", request->method.c_str());
	}

	close(connfd);
}

// run the web server
void WebServer::run() {
	logger->info("starting WebServer with listening port: %ld, www_root=%s",
			serverPort, www_root.c_str());

	int listenfd;
	socklen_t len;
	struct sockaddr_in6 servaddr;

	// create socket for listening
	if ((listenfd = socket(AF_INET6, SOCK_STREAM, 0)) < 0) {
		perror("socket");
		logger->error("failed to create socket!");
		exit(EXIT_FAILURE);
	}

	// bind to the port and all interface addresses
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin6_family = AF_INET6;
	servaddr.sin6_addr = in6addr_any;
	servaddr.sin6_port = htons(serverPort);

	if (bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) {
		perror("bind");
		logger->error("failed to bind to port %ld!", serverPort);
		exit(EXIT_FAILURE);
	}

	// Set the socket to passive mode, with specified listen queue size
	if (listen(listenfd, LISTENQ) < 0) {
		perror("listen");
		logger->error("failed to listen on port %ld!", serverPort);
		exit(EXIT_FAILURE);
	}

	// Start waiting for incoming connections, until user interrupts
	for (;;) {

		// wait for incoming connection
		struct sockaddr_in6 cliaddr;
		len = sizeof(cliaddr);

		int connfd = accept(listenfd, (struct sockaddr *) (&cliaddr), &len);

		if (connfd < 0) {
			perror("accept");
			exit(EXIT_FAILURE);
		}

	    char buff[80];
	    logger->debug("connection from %s, port %d\n",
					inet_ntop(AF_INET6, &cliaddr.sin6_addr, buff, sizeof(buff)),
					ntohs(cliaddr.sin6_port));

		// fire off the new thread and serve the request
		WebServerThreadParams * webserverThreadParams = new WebServerThreadParams;
		webserverThreadParams->webServer = this;
		webserverThreadParams->connfd = connfd;
		pthread_t serviceThread;
		pthread_create(&serviceThread, NULL, serveRequest, webserverThreadParams);
	}
}

uint32_t WebServer::getServerPort() const
{
    return serverPort;
}

string WebServer::getWwwRoot() const
{
    return www_root;
}

void WebServer::doGet(HttpRequest request, int connfd)
{

	logger->debug("doGet");

	HttpResponse * response = new HttpResponse();

	if (HttpHelper::isDirectoryListing(request)) {

		logger->debug("listing directories!");

		// This is the special "index" object. Let's list our directory to the client
		char command[200];
		sprintf(command, "ls %s", getWwwRoot().c_str());

		FILE *fp = popen(command, "r");

		if (NULL == fp) {
			logger->error("failed to list www_root!");
			response->setStatus(403, "Forbidden");
			sendResponse(response, connfd);
			return ;
		}

		stringstream oss;
		char pipeData[1000];
		memset(pipeData, 0, sizeof(pipeData));

		size_t dataRead = fread(pipeData, 1, sizeof(pipeData), fp);
		while (dataRead > 0){
			oss << pipeData;

			dataRead = fread(pipeData, 1, sizeof(pipeData), fp);
		}
		pclose (fp);

		// now all directory info is in oss
		response->setStatus(200, "OK");
		string dirInfoStr = oss.str();

		int contentLength = dirInfoStr.length();

		response->setContentLength(contentLength);

		int headerLength = response->generateHeadersStrings().length();
		// let's put all the headers and content in one big buffer and flush it out to the client
		char * responseData = (char *) malloc(headerLength+contentLength);
		memcpy(responseData, response->generateHeadersStrings().c_str(), headerLength);
		memcpy(responseData+headerLength, dirInfoStr.c_str(), contentLength);

		Utils::flushOutputBuffer(connfd, responseData, headerLength+contentLength);

		free(responseData);
		return;
	}

	string localFilePath = getWwwRoot()+"/"+request.getResourcePath();
	int32_t fileSize = Utils::getFileSize(localFilePath.c_str());

	if (-1 == fileSize) {
		logger->error("Failed to open %s", localFilePath.c_str());

		// we should return 404 here
		response->setStatus(404, "Not Found");
		sendResponse(response, connfd);
		return ;
	}

	//
	int fd = open(localFilePath.c_str(), O_RDONLY);

	if (fd == -1) {
		logger->error("Failed to open %s", localFilePath.c_str());
		response->setStatus(404, "Not Found");

		sendResponse(response, connfd);
		return ;
	}

	response->setStatus(200, "OK");

	// set the content length
	response->setContentLength(fileSize);

	string httpStr = response->generateHeadersStrings();

	logger->debug("start to push content to client");

	if (-1 == HttpHelper::pushHttpMsg(httpStr, connfd, fd)) {
		logger->error("failed to send HTTP message!");
		return;
	}

	logger->debug("finished pushing content to client");

}

void WebServer::sendResponse(HttpResponse * response, int connfd)
{
    string responseStr = response->generateHeadersStrings();
    Utils::flushOutputBuffer(connfd, (char*)(responseStr.c_str()), (int)(responseStr.length()));
}

int WebServer::getThreadNumber()
{
	return threadNumber;
}

/**
 * serve the PUT request
 * request: the http request received
 * connfd: the socket fd
 * dataReadSoFar is the buffer where the initial content sits
 * dataLengthReadSoFar is the length of the initial data that has been read so far
 */
void WebServer::doPut(HttpRequest request, int connfd, char *dataReadSoFar, int dataLengthReadSoFar)
{
	long contentLength = request.getContentLength();
	long contentReceived = dataLengthReadSoFar;

	HttpResponse * response = new HttpResponse();

	string localFilePath = getWwwRoot()+"/"+request.getResourcePath();

	if (contentLength == 0) {
		logger->debug("PUT request is to delete file %s",
				localFilePath.c_str());

		if (remove(localFilePath.c_str()) != 0) {
			perror("Error deleting file");
			logger->error("failed to remove %s", localFilePath.c_str());
		}

		response->setStatus(200, "OK");
		sendResponse(response, connfd);
		return;
	}

	string tmpLocalFilePath = localFilePath + ".tmp";

	// open the temp file for writing and we'll rename it to real file later
	FILE * receivedFile = fopen(tmpLocalFilePath.c_str(), "wb");

	// if open failed, let's return 403 response
	if (NULL == receivedFile) {
		logger->error("failed to create file \"%s\"", tmpLocalFilePath.c_str());

		response->setStatus(403, "Forbidden");
		sendResponse(response, connfd);
		return ;
	}

	//write the initial part of the content to the file
	fwrite(dataReadSoFar, 1, dataLengthReadSoFar, receivedFile);

	char buffer[1024];
	memset(buffer, 0, sizeof(buffer));

	// read more from the connection
	int bufsize = read(connfd, buffer, sizeof(buffer));

	while ((0 != bufsize) && (-1 != bufsize) && (!serverShutingDown)) {

		// write data to file
		fwrite(buffer, 1, bufsize, receivedFile);
		fprintf(stderr, ".");

		contentReceived += bufsize;

		if (contentReceived < contentLength) {
			//continue to read more
			memset(buffer, 0, sizeof(buffer));
			bufsize = read(connfd, buffer, sizeof(buffer));
		} else {
			break;
		}
	}

	fclose(receivedFile);

	if (contentReceived < contentLength) {
		// if we exit the while loop due to error or shutdown
		// remove the temp file
		if (remove(tmpLocalFilePath.c_str()) != 0) {
			perror("Error deleting file");
			logger->error("failed to remove %s", localFilePath.c_str());
		}
		return;
	} else {
		// rename the temp file to real file name
		if (-1 == rename(tmpLocalFilePath.c_str(), localFilePath.c_str())) {
			logger->error("failed to rename file from %s to %s\n",
					tmpLocalFilePath.c_str(), localFilePath.c_str());

			response->setStatus(403, "Forbidden");
			sendResponse(response, connfd);
			return ;
		}
	}

	logger->debug("saved push content to %s ", localFilePath.c_str());

	response->setStatus(200, "OK");
	sendResponse(response, connfd);

	return ;
}

void WebServer::increamentThreadNumber()
{
	pthread_mutex_lock(&threadNumberMutex);
	this->threadNumber ++;
	pthread_mutex_unlock(&threadNumberMutex);
}

void WebServer::decrementThreadNumber()
{
	pthread_mutex_lock(&threadNumberMutex);
	this->threadNumber --;
	pthread_mutex_unlock(&threadNumberMutex);
}

void WebServer::gracefulShutDown()
{
	logger->info("gracefulShutDown!");

	this->serverShutingDown = true;

	while (getThreadNumber() > 0) {
		// wait for running thread to exit;
		sleep(2);
	}
}









